package com.qianya.system.face.web.patient.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qianya.model.common.CommonResult;
import com.qianya.model.common.ResultCode;
import com.qianya.system.face.config.RedisUtil;
import com.qianya.system.face.web.patient.faceutil.dto.FaceRecognitionResDTO;
import com.qianya.system.face.web.patient.faceutil.entity.ProcessInfo;
import com.qianya.system.face.web.patient.faceutil.factory.FaceEngineFactory;
import com.qianya.system.face.web.patient.faceutil.rpc.BusinessException;
import com.qianya.system.face.web.patient.faceutil.rpc.ErrorCodeEnum;
import com.qianya.system.face.util.Base64Util;
import com.qianya.system.face.web.patient.model.PatientFileInfo;
import com.qianya.system.face.web.patient.model.PatientHisPrescription;
import com.qianya.system.face.web.patient.service.FaceEngineService;
import com.arcsoft.face.*;
import com.google.common.collect.Lists;
import com.qianya.system.face.util.UserRamCache;
import com.qianya.system.face.web.patient.service.IPatientFileInfoService;
import com.qianya.system.face.web.patient.service.IPatientHisPrescriptionService;
import com.qianya.util.FileUtil;
import com.qianya.util.SystemConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.*;
import java.util.concurrent.*;


@Service
@Component
@Slf4j
public class FaceEngineServiceImpl implements FaceEngineService {


    @Value("${config.arcface-sdk.sdk-lib-path}")
    public String sdkLibPath;
    @Value("${config.arcface-sdk.app-id}")
    public String appId;

    @Value("${config.arcface-sdk.sdk-key}")
    public String sdkKey;

    @Value("${config.arcface-sdk.detect-pool-size}")
    public Integer detectPooSize;

    @Value("${config.arcface-sdk.compare-pool-size}")
    public Integer comparePooSize;

    private ExecutorService compareExecutorService;
    @Autowired
    private IPatientFileInfoService fileInfoService;
    @Autowired
    private IPatientHisPrescriptionService patientHisPrescriptionService;
    //通用人脸识别引擎池
    private GenericObjectPool<FaceEngine> faceEngineGeneralPool;

    //人脸比对引擎池
    private GenericObjectPool<FaceEngine> faceEngineComparePool;
    /**
     * @desc 配置初始化
     * @author lhl
     * @date 2020-07-02 09:24
     * @return
    */
    @PostConstruct
    public void init() {
        GenericObjectPoolConfig detectPoolConfig = new GenericObjectPoolConfig();
        detectPoolConfig.setMaxIdle(detectPooSize);
        detectPoolConfig.setMaxTotal(detectPooSize);
        detectPoolConfig.setMinIdle(detectPooSize);
        detectPoolConfig.setLifo(false);
        EngineConfiguration detectCfg = new EngineConfiguration();
        FunctionConfiguration detectFunctionCfg = new FunctionConfiguration();
        detectFunctionCfg.setSupportFaceDetect(true);//开启人脸检测功能
        detectFunctionCfg.setSupportFaceRecognition(true);//开启人脸识别功能
        detectFunctionCfg.setSupportAge(false);//开启年龄检测功能
        detectFunctionCfg.setSupportGender(false);//开启性别检测功能
        detectFunctionCfg.setSupportLiveness(false);//开启活体检测功能
        detectCfg.setFunctionConfiguration(detectFunctionCfg);
        detectCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);//图片检测模式，如果是连续帧的视频流图片，那么改成VIDEO模式
        detectCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);//人脸旋转角度
        faceEngineGeneralPool = new GenericObjectPool(new FaceEngineFactory(sdkLibPath, appId, sdkKey, null, detectCfg), detectPoolConfig);//底层库算法对象池

        //初始化特征比较线程池
        GenericObjectPoolConfig comparePoolConfig = new GenericObjectPoolConfig();
        comparePoolConfig.setMaxIdle(comparePooSize);
        comparePoolConfig.setMaxTotal(comparePooSize);
        comparePoolConfig.setMinIdle(comparePooSize);
        comparePoolConfig.setLifo(false);
        EngineConfiguration compareCfg = new EngineConfiguration();
        FunctionConfiguration compareFunctionCfg = new FunctionConfiguration();
        compareFunctionCfg.setSupportFaceRecognition(true);//开启人脸识别功能
        compareCfg.setFunctionConfiguration(compareFunctionCfg);
        compareCfg.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);//图片检测模式，如果是连续帧的视频流图片，那么改成VIDEO模式
        compareCfg.setDetectFaceOrientPriority(DetectOrient.ASF_OP_0_ONLY);//人脸旋转角度
        faceEngineComparePool = new GenericObjectPool(new FaceEngineFactory(sdkLibPath, appId, sdkKey, null, compareCfg), comparePoolConfig);//底层库算法对象池
        compareExecutorService = Executors.newFixedThreadPool(comparePooSize);
    }
    /**
     * @desc  档案添加
     * @author lhl
     * @date 2020-07-02 09:22
     * @return
    */
    public CommonResult faceAdd(PatientFileInfo fileInfo) {
        if (null==fileInfo.getFilePatientName()){
            return  CommonResult.error(ResultCode.ERROR,"患者名不能为空");
        }
        String path= FileUtil.base64UpLoad(fileInfo.getFilePatientIcon());
        String absolutePath=path.replace("\\statics", SystemConfig.filePath());
        if ("error".equals(path)){
            return  CommonResult.error(ResultCode.IMAGES_ExCEPTION,"图片格式错误");
        }
        fileInfo.setFilePatientIcon(path);
        File files = new File(absolutePath);
        ImageInfo rgbData = ImageFactory.getRGBData(files);
        List<FaceInfo> faceInfoList = this.detectFaces(rgbData);
        if (CollectionUtil.isNotEmpty(faceInfoList)) {
            byte[] feature = this.extractFaceFeature(rgbData, faceInfoList.get(0));
            if (null==fileInfo.getFileId()){
                fileInfo.setFileId(IdUtil.simpleUUID());
            }
            fileInfo.setFilePatientGender(1);
            fileInfo.setFileFaceFeature(feature);
            fileInfo.setFilePatientAge(RandomUtil.randomInt(20,70));
            fileInfo.setFilePatientTel("135"+RandomUtil.randomInt(100,999)+"22"+RandomUtil.randomInt(100,999));
            fileInfo.setFilePatientIdNum("500235********1017");
            fileInfo.setFileHospitalNum(String.valueOf(RandomUtil.randomInt(1,9999)));
            fileInfo.setFileAdmissionDate(new Date());
            if (fileInfoService.save(fileInfo)){
                UserRamCache.addUser(fileInfo);
            }
        }
        return CommonResult.success();
    }
    /**
     * @desc 根据人员身份证号或者his号或手机号查询医嘱
     * @author lhl
     * @date 2020-08-10 11:46
     * @return
    */
    @Override
    public CommonResult selectPatientAdvice(String id) {
        PatientFileInfo pfi=fileInfoService.getOne(new QueryWrapper<PatientFileInfo>()
                .eq("file_patient_id_num",id)
                .or().eq("file_hospital_num",id)
                .or().eq("file_patient_tel",id));
        if (pfi==null){
            return CommonResult.error(ResultCode.PATIENT_NOT_FOUND,"未查询到该患者");
        }
        CommonResult commonResult=patientHisPrescriptionService.view(pfi.getFileId());

        if (commonResult.getCode()==666){

            return commonResult;
        }
        List<FaceRecognitionResDTO> faceRecognitionResDTOList = Lists.newLinkedList();
        FaceRecognitionResDTO faceRecognitionResDTO = new FaceRecognitionResDTO();
        faceRecognitionResDTO.setFileInfo(pfi);
        faceRecognitionResDTO.setHisPrescription((List<PatientHisPrescription>) commonResult.getBean());
        faceRecognitionResDTOList.add(faceRecognitionResDTO);
        return CommonResult.success(faceRecognitionResDTOList);
    }

    /**
     * @desc 人脸识别
     * @author lhl
     * @date 2020-07-02 09:27
     * @param  image
     * @return
     */
    public CommonResult faceRecognition(String image) {
        List<FaceRecognitionResDTO> faceRecognitionResDTOList = Lists.newLinkedList();
        byte[] bytes = Base64Util.base64ToBytes(image);
        ImageInfo rgbData = ImageFactory.getRGBData(bytes);
        List<FaceInfo> faceInfoList = this.detectFaces(rgbData);
        if (CollectionUtil.isNotEmpty(faceInfoList)) {
            for (FaceInfo faceInfo : faceInfoList) {
                FaceRecognitionResDTO faceRecognitionResDTO = new FaceRecognitionResDTO();
                faceRecognitionResDTO.setRect(faceInfo.getRect());
                byte[] feature = this.extractFaceFeature(rgbData, faceInfo);
              if (feature != null) {
                    List<PatientFileInfo> userCompareInfos = this.faceRecognitions(feature, UserRamCache.getUserList(), 0.8f);
                    if (CollectionUtil.isNotEmpty(userCompareInfos)) {
                        faceRecognitionResDTO.setFileInfo((PatientFileInfo) RedisUtil.hget("fileInfo",userCompareInfos.get(0).getFileId()));
                        faceRecognitionResDTO.setSimilar(userCompareInfos.get(0).getSimilar());

                        CommonResult commonResult=patientHisPrescriptionService.view(userCompareInfos.get(0).getFileId());

                        if (commonResult.getCode()==996 || commonResult.getCode()==997){

                            return commonResult;
                        }
                        faceRecognitionResDTO.setHisPrescription((List<PatientHisPrescription>) commonResult.getBean());
                    }else{ //人脸检测到数据库没信息
                        return CommonResult.error(ResultCode.PATIENT_NOT_FOUND,"未查询到该患者");
                    }
                }else { //图片上没有人脸
                  return CommonResult.error(ResultCode.PATIENT_NOT_FOUND,"未查询到该患者");
              }
                faceRecognitionResDTOList.add(faceRecognitionResDTO);
            }
        }else{ //

            return CommonResult.error(ResultCode.FACE_NOT_FOUND,"人脸未检测到");
        }
        Collections.sort(faceRecognitionResDTOList);
        return CommonResult.success(faceRecognitionResDTOList);
    }
    /**
     * @desc 识别中。。。
     * @author lhl
     * @date 2020-07-02 09:27
     * @param  * @param null
     * @return
     */
    public List<PatientFileInfo> faceRecognitions(byte[] faceFeature, List<PatientFileInfo> userInfoList, float passRate) {
        List<PatientFileInfo> resultUserInfoList = Lists.newLinkedList();//识别到的人脸列表

        FaceFeature targetFaceFeature = new FaceFeature();
        targetFaceFeature.setFeatureData(faceFeature);

        List<List<PatientFileInfo>> faceUserInfoPartList = Lists.partition(userInfoList, 1000);//分成1000一组，多线程处理
        CompletionService<List<PatientFileInfo>> completionService = new ExecutorCompletionService(compareExecutorService);
        for (List<PatientFileInfo> part : faceUserInfoPartList) {
            completionService.submit(new CompareFaceTask(part, targetFaceFeature, passRate));
        }
        for (int i = 0; i < faceUserInfoPartList.size(); i++) {
            List<PatientFileInfo> faceUserInfoList = null;
            try {
                faceUserInfoList = completionService.take().get();
            } catch (InterruptedException | ExecutionException e) {
            }
            if (CollectionUtil.isNotEmpty(userInfoList)) {
                resultUserInfoList.addAll(faceUserInfoList);
            }
        }

        resultUserInfoList.sort((h1, h2) -> h2.getSimilar().compareTo(h1.getSimilar()));//从大到小排序

        return resultUserInfoList;
    }
    /**
     * @desc 人脸检测
     * @author lhl
     * @date 2020-07-02 09:25
     * @return
    */
    @Override
    public List<FaceInfo> detectFaces(ImageInfo imageInfo) {

        FaceEngine faceEngine = null;
        try {
            faceEngine = faceEngineGeneralPool.borrowObject();
            if (faceEngine == null) {
                throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
            }

            //人脸检测得到人脸列表
            List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
            //人脸检测
            int resultCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
            if (resultCode == 0) {
                return faceInfoList;
            } else {
                log.error("人脸检测失败，resultCode：" + resultCode);
            }

        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineGeneralPool.returnObject(faceEngine);
            }
        }

        return null;

    }
    /**
     * @desc 人脸比较
     * @author lhl
     * @date 2020-07-02 09:25
     * @return
    */
    @Override
    public Float compareFace(ImageInfo imageInfo1, ImageInfo imageInfo2) {

        List<FaceInfo> faceInfoList1 = detectFaces(imageInfo1);
        List<FaceInfo> faceInfoList2 = detectFaces(imageInfo2);

        if (CollectionUtil.isEmpty(faceInfoList1) || CollectionUtil.isEmpty(faceInfoList2)) {
            throw new BusinessException(ErrorCodeEnum.FAIL,"未检测到人脸");
        }

        byte[] feature1 = extractFaceFeature(imageInfo1, faceInfoList1.get(0));
        byte[] feature2 = extractFaceFeature(imageInfo2, faceInfoList2.get(0));

        FaceEngine faceEngine = null;
        try {
            faceEngine = faceEngineGeneralPool.borrowObject();
            if (faceEngine == null) {
                throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
            }

            FaceFeature faceFeature1 = new FaceFeature();
            faceFeature1.setFeatureData(feature1);
            FaceFeature faceFeature2 = new FaceFeature();
            faceFeature2.setFeatureData(feature2);
            //提取人脸特征
            FaceSimilar faceSimilar = new FaceSimilar();
            int resultCode = faceEngine.compareFaceFeature(faceFeature1, faceFeature2, faceSimilar);
            if (resultCode == 0) {
                return faceSimilar.getScore();
            } else {
                log.error("特征提取失败，resultCode：" + resultCode);
            }

        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineGeneralPool.returnObject(faceEngine);
            }
        }

        return null;

    }

    /**
     * 人脸特征
     *
     * @param imageInfo
     * @return
     */
    @Override
    public synchronized byte[]  extractFaceFeature(ImageInfo imageInfo, FaceInfo faceInfo) {

        FaceEngine faceEngine = null;
        try {
            faceEngine = faceEngineGeneralPool.borrowObject();
            if (faceEngine == null) {
                throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
            }

            FaceFeature faceFeature = new FaceFeature();
            //提取人脸特征
            int resultCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfo, faceFeature);
            if (resultCode == 0) {
                return faceFeature.getFeatureData();
            } else {
                log.error("特征提取失败，resultCode：" + resultCode);
            }

        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineGeneralPool.returnObject(faceEngine);
            }
        }

        return null;

    }



    @Override
    public List<ProcessInfo> process(ImageInfo imageInfo, List<FaceInfo> faceInfoList) {
        FaceEngine faceEngine = null;
        try {
            //获取引擎对象
            faceEngine = faceEngineGeneralPool.borrowObject();
            if (faceEngine == null) {
                throw new BusinessException(ErrorCodeEnum.FAIL, "获取引擎失败");
            }


            int resultCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, FunctionConfiguration.builder().supportAge(true).supportGender(true).supportLiveness(true).build());
            if (resultCode == 0) {
                List<ProcessInfo> processInfoList = Lists.newLinkedList();

                //性别列表
                List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
                faceEngine.getGender(genderInfoList);

                //年龄列表
                List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
                faceEngine.getAge(ageInfoList);
                //活体结果列表
                List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
                faceEngine.getLiveness(livenessInfoList);


                for (int i = 0; i < genderInfoList.size(); i++) {
                    ProcessInfo processInfo = new ProcessInfo();
                    processInfo.setGender(genderInfoList.get(i).getGender());
                    processInfo.setAge(ageInfoList.get(i).getAge());
                    processInfo.setLiveness(livenessInfoList.get(i).getLiveness());
                    processInfoList.add(processInfo);
                }
                return processInfoList;

            }


        } catch (Exception e) {
            log.error("", e);
        } finally {
            if (faceEngine != null) {
                //释放引擎对象
                faceEngineGeneralPool.returnObject(faceEngine);
            }
        }

        return null;

    }


    private class CompareFaceTask implements Callable<List<PatientFileInfo>> {

        private List<PatientFileInfo> userInfoList;
        private FaceFeature targetFaceFeature;
        private float passRate;


        public CompareFaceTask(List<PatientFileInfo> userInfoList, FaceFeature targetFaceFeature, float passRate) {
            this.userInfoList = userInfoList;
            this.targetFaceFeature = targetFaceFeature;
            this.passRate = passRate;
        }

        @Override
        public List<PatientFileInfo> call() throws Exception {
            FaceEngine faceEngine = null;
            List<PatientFileInfo> resultUserInfoList = Lists.newLinkedList();//识别到的人脸列表
            try {
                faceEngine = faceEngineComparePool.borrowObject();
                for (PatientFileInfo userInfo : userInfoList) {
                    FaceFeature sourceFaceFeature = new FaceFeature();
                    sourceFaceFeature.setFeatureData(userInfo.getFileFaceFeature());
                    FaceSimilar faceSimilar = new FaceSimilar();
                    faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
                    if (faceSimilar.getScore() > passRate) {//相似值大于配置预期，加入到识别到人脸的列表
                        PatientFileInfo info = new PatientFileInfo();
                        info.setFilePatientName(userInfo.getFilePatientName());
                        info.setFileId(userInfo.getFileId());
                        info.setSimilar(faceSimilar.getScore());
                        resultUserInfoList.add(info);
                    }
                }
            } catch (Exception e) {
                log.error("", e);
            } finally {
                if (faceEngine != null) {
                    faceEngineComparePool.returnObject(faceEngine);
                }
            }
            return resultUserInfoList;
        }

    }
}
